package controllers

import (
	"context"

	"git.blindage.org/21h/single-redis-operator/controllers/manifests"
	v1 "k8s.io/api/apps/v1"

	sredisv1 "git.blindage.org/21h/single-redis-operator/api/v1"

	raven "github.com/getsentry/raven-go"
	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// ReconcileConfigmap means full reconcile of configmap
func (r *SingleRedisReconciler) ReconcileConfigmap(reqLogger logr.Logger, cr *sredisv1.SingleRedis, configmapName string, configmapData map[string]string) (reconcile.Result, error) {

	newConfigmap := manifests.GenerateConfigmap(cr, configmapName, configmapData)

	if err := controllerutil.SetControllerReference(cr, &newConfigmap, r.Scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	foundConfigmap := corev1.ConfigMap{}
	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: newConfigmap.Name, Namespace: newConfigmap.Namespace}, &foundConfigmap)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a Sentinel Configmap", "Namespace", newConfigmap.Namespace, "Name", newConfigmap.Name)
		err = r.Client.Create(context.TODO(), &newConfigmap)
		if err != nil {
			reqLogger.Info("Creating Sentinel Configmap error", "Namespace", newConfigmap.Namespace, "Name", newConfigmap.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileConfigmapApply(foundConfigmap, newConfigmap); reconcileRequired {
			reqLogger.Info("Updating Sentinel Configmap", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.Client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Sentinel Configmap error", "Namespace", foundConfigmap.Namespace, "Name", foundConfigmap.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}

	reqLogger.Info("Reconcile configmap complete", "Configmap Name", configmapName, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileService one function to reconcile all services
func (r *SingleRedisReconciler) ReconcileService(reqLogger logr.Logger, cr *sredisv1.SingleRedis, serviceName string, ports map[string]int32, selector map[string]string) (reconcile.Result, error) {

	// reconcile Service
	newService := manifests.GenerateService(cr, serviceName, ports, selector)

	if err := controllerutil.SetControllerReference(cr, &newService, r.Scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	foundService := corev1.Service{}
	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: newService.Name, Namespace: newService.Namespace}, &foundService)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a new Service", "Namespace", newService.Namespace, "Name", newService.Name)
		err = r.Client.Create(context.TODO(), &newService)
		if err != nil {
			reqLogger.Info("Creating Service error", "Namespace", newService.Namespace, "Name", newService.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileServiceApply(foundService, newService); reconcileRequired {
			reqLogger.Info("Updating Service", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.Client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Service error", "Namespace", foundService.Namespace, "Name", foundService.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}
	reqLogger.Info("Reconcile service complete", "Service Name", serviceName, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}

// ReconcileStatefulSet one function to reconcile all StatefulSets
func (r *SingleRedisReconciler) ReconcileStatefulSet(reqLogger logr.Logger, cr *sredisv1.SingleRedis, newStatefulset v1.StatefulSet) (reconcile.Result, error) {

	if err := controllerutil.SetControllerReference(cr, &newStatefulset, r.Scheme); err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	}

	// controller statefulset
	foundStatefulset := v1.StatefulSet{}
	err := r.Client.Get(context.TODO(), types.NamespacedName{Name: newStatefulset.Name, Namespace: newStatefulset.Namespace}, &foundStatefulset)
	if err != nil && errors.IsNotFound(err) {
		reqLogger.Info("Creating a new Redis Statefulset", "Namespace", newStatefulset.Namespace, "Name", newStatefulset.Name)
		err = r.Client.Create(context.TODO(), &newStatefulset)
		if err != nil {
			reqLogger.Info("Creating Redis Statefulset error", "Namespace", newStatefulset.Namespace, "Name", newStatefulset.Name, "Error", err)
			raven.CaptureErrorAndWait(err, nil)
			return reconcile.Result{}, err
		}
	} else if err != nil {
		raven.CaptureErrorAndWait(err, nil)
		return reconcile.Result{}, err
	} else {
		if reconcileRequired, reconResource := reconcileStatefulsetApply(foundStatefulset, newStatefulset); reconcileRequired {
			reqLogger.Info("Updating Redis Statefulset", "Namespace", reconResource.Namespace, "Name", reconResource.Name)
			if err = r.Client.Update(context.TODO(), &reconResource); err != nil {
				reqLogger.Info("Reconcile Redis Statefulset error", "Namespace", foundStatefulset.Namespace, "Name", foundStatefulset.Name, "Error", err)
				raven.CaptureErrorAndWait(err, nil)
				return reconcile.Result{}, err
			}
		}
	}

	reqLogger.Info("Reconcile StatefulSet complete", "StatefulSet Name", newStatefulset.Name, "Namespace", cr.Namespace, "Name", cr.Name)
	return reconcile.Result{}, nil
}
